// unicode.cpp - UNICODE test program
// NCC - Copyright (C) 2012 Willow Schlanger <wschlanger@gmail.com>
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list
//    of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other materials
//    provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "../util/ncc_util.h"

#include <stdexcept>
#include <iostream>

// sample function to illustrate how to use the above code.
void write_utf16le_from_utf32(const char *ofname, NetwideCompilerCollection::NccRawCharacterSource &src2)
{
	using namespace NetwideCompilerCollection;
	using namespace std;
	
	if(src2.format() != NccRawCharacterSource::FORMAT_UTF32)
		throw std::runtime_error("source is not UTF-32");

	NccUtf16FromUtf32CharacterSource src(src2);

	if(!src2.status().empty())
	{
		throw std::runtime_error(std::string("encountered error: ") + src.status());
	}
	
	FILE *fo = NccCommonUtils::universalOpen(ofname, "wb");
	if(fo == NULL)
		throw std::runtime_error(std::string("unable to open file for writing: ") + std::string(ofname));
	
	// Write UTF-16-LE Byte Order Mark.
	fputc(0xff, fo);
	fputc(0xfe, fo);

	// While we get valid characters, send them to the output file.
	while(src.current() >= 0 && src.current() <= 0xffff)
	{
		// note: even if src.current() is '\r' or '\n', just copy to output.
		// for the purpose of this sample we don't want to normalize or convert CR/LF's.
		fputc(src.current() & 0xff, fo);
		fputc((src.current() >> 8) & 0xff, fo);
		src.next();
	}
	
	fclose(fo);
	if(src.current() != NCC_STATUS_EOS)
	{
		// -1 means EOS. -2 means ERROR.
		if(src.current() == NCC_STATUS_ERROR && !src.status().empty())
		{
			throw std::runtime_error(std::string("encountered error: ") + src.status());
		}
		throw std::runtime_error("internal error (6)");
	}
}

void write_utf16be_from_utf32(const char *ofname, NetwideCompilerCollection::NccRawCharacterSource &src2)
{
	using namespace NetwideCompilerCollection;
	using namespace std;
	
	if(src2.format() != NccRawCharacterSource::FORMAT_UTF32)
		throw std::runtime_error("source is not UTF-32");

	NccUtf16FromUtf32CharacterSource src(src2);

	if(!src2.status().empty())
	{
		throw std::runtime_error(std::string("encountered error: ") + src.status());
	}
	
	FILE *fo = fopen(ofname, "wb");
	if(fo == NULL)
		throw std::runtime_error(std::string("unable to open file for writing: ") + std::string(ofname));
	
	// Write UTF-16-BE Byte Order Mark.
	fputc(0xfe, fo);
	fputc(0xff, fo);

	// While we get valid characters, send them to the output file.
	while(src.current() >= 0 && src.current() <= 0xffff)
	{
		// note: even if src.current() is '\r' or '\n', just copy to output.
		// for the purpose of this sample we don't want to normalize or convert CR/LF's.
		fputc((src.current() >> 8) & 0xff, fo);
		fputc(src.current() & 0xff, fo);
		src.next();
	}
	
	fclose(fo);
	if(src.current() != NCC_STATUS_EOS)
	{
		// -1 means EOS. -2 means ERROR.
		if(src.current() == NCC_STATUS_ERROR && !src.status().empty())
		{
			throw std::runtime_error(std::string("encountered error: ") + src.status());
		}
		throw std::runtime_error("internal error (6)");
	}
}

void write_utf8_from_utf32(const char *ofname, NetwideCompilerCollection::NccRawCharacterSource &src2)
{
	using namespace NetwideCompilerCollection;
	using namespace std;
	
	if(src2.format() != NccRawCharacterSource::FORMAT_UTF32)
		throw std::runtime_error("source is not UTF-32");

	NccUtf8FromUtf32CharacterSource src(src2);

	if(!src2.status().empty())
	{
		throw std::runtime_error(std::string("encountered error: ") + src.status());
	}
	
	FILE *fo = fopen(ofname, "wb");
	if(fo == NULL)
		throw std::runtime_error(std::string("unable to open file for writing: ") + std::string(ofname));
	
	// Write UTF-8 Byte Order Mark.
	fputc(0xef, fo);
	fputc(0xbb, fo);
	fputc(0xbf, fo);

	// While we get valid characters, send them to the output file.
	while(src.current() >= 0 && src.current() <= 0xff)
	{
		// note: even if src.current() is '\r' or '\n', just copy to output.
		// for the purpose of this sample we don't want to normalize or convert CR/LF's.
		fputc(src.current(), fo);
		src.next();
	}
	
	fclose(fo);
	if(src.current() != NCC_STATUS_EOS)
	{
		// -1 means EOS. -2 means ERROR.
		if(src.current() == NCC_STATUS_ERROR && !src.status().empty())
		{
			throw std::runtime_error(std::string("encountered error: ") + src.status());
		}
		throw std::runtime_error("internal error (6)");
	}
}

class Utf32TestGenerator :
	public NetwideCompilerCollection::NccRawCharacterSource
{
	NetwideCompilerCollection::S4 curChar;
public:
	virtual NetwideCompilerCollection::NccSourceQueryObject setupQuery(const char *name, int out_type)
	{
		return NetwideCompilerCollection::NccSourceQueryObject();
	}

	Utf32TestGenerator()
	{
		curChar = 0;
	}

	virtual ~Utf32TestGenerator() { }

	virtual NetwideCompilerCollection::S4 current()
	{
		return curChar;
	}

	virtual void next()
	{
		if(curChar < 0)
			return;
		++curChar;
		if(curChar == 0xd800)
			curChar = 0xe000;
		if(curChar == 0xfffe)
			curChar = 0x10000;
		if(curChar == 0x110000)
			curChar = -1;
	}

	virtual void close()
	{
		curChar = -1;
	}
	
	// This returns the current format.
	// If neither current() nor next() have been called yet, the file has not been
	// read yet. In that case, format() could cause a file read to get the BOM.
	virtual int format()
	{
		return FORMAT_UTF32;
	}
	
	virtual bool sourceHasBom()
	{
		return false;
	}
	
	virtual std::string status()
	{
		return "";
	}
};

void do_test_suite()
{
	using namespace std;
	using namespace NetwideCompilerCollection;
	
	std::cout << "Generating files... " << std::flush;
	{
		Utf32TestGenerator testSource;
		write_utf16le_from_utf32("utf16le_test_out.txt", testSource);
	}
	{
		Utf32TestGenerator testSource;
		write_utf16be_from_utf32("utf16be_test_out.txt", testSource);
	}
	{
		Utf32TestGenerator testSource;
		write_utf8_from_utf32("utf8_test_out.txt", testSource);
	}
	std::cout << "done" << std::endl;
	
	// Decode each file back to UTF-32 and compare.
	const char *filenames[] = {"utf16le_test_out.txt", "utf16be_test_out.txt", "utf8_test_out.txt", NULL};
	for(const char **fn = filenames; *fn != NULL; ++fn)
	{
		std::cout << "\nTranslating " << *fn << "..." << std::endl;
		
		NccCFileByteStream is(fopen(*fn, "rb"), false, false);

		// Create a generic UTF character source that reads from the byte stream.
		NccUtfCharacterSource src1(is);

		// Create a UTF-32 character source that reads from the generic UTF character source.
		// Fail on invalid characters, so we can remove them.
		NccUtf32CharacterSource src2(src1, true /* fail on invalid characters */);
		
		Utf32TestGenerator correct;
		S4 lastGood = -1;
		while(src2.current() >= 0)
		{
			lastGood = src2.current();
			if(lastGood != correct.current())
			{
				std::cout << "Discrepency found!!! Wanted: " << std::hex << correct.current() << std::dec << std::endl;
				break;
			}
			src2.next();
			correct.next();
		}
		std::cout << "Message = [" << src2.status() << "]" << std::endl;
		std::cout << std::hex << lastGood << std::dec << std::endl;
	}
}

int main()
{
	do_test_suite();
	return 0;
}
